import sys
import os
import glob
import fileinput
import logging

from optparse import OptionParser

###################################################################################################
# Utilities
###################################################################################################

def diff(a, b):
        b = set(b)
        return [aa for aa in a if aa not in b]

def union(a, b):
        b = set(b)
        return [aa for aa in a if aa in b]

def abspath(list):
  return [os.path.abspath(item) for item in list]

###################################################################################################
# Glob
###################################################################################################
  
def load_rules(exclude_file):
  # Load exclusion rules
  exclude_rules = open(exclude_file, "r").readlines()
  exclude_rules = [line.rstrip("\r\n") for line in exclude_rules]

  # A blank line matches no files, so it can serve as a separator for readability.
  exclude_rules = [line for line in exclude_rules if not len(line) == 0]

  # A line starting with # serves as a comment.
  exclude_rules = [line for line in exclude_rules if not line[0:1] == "#"]

  # TODO: the way this is setup right now a ! pattern will override all negative patterns
  # An optional prefix ! which negates the pattern; any matching file excluded by
  # a previous pattern will become included again. If a negated pattern matches,
  # this will override lower precedence patterns sources.
  include_rules = [line for line in exclude_rules if line[0:1] == "!"]
  exclude_rules = [line for line in exclude_rules if not line[0:1] == "!"]

  return exclude_rules, include_rules

def match_files(dir, exclude_rules, include_rules):
  working_dir = os.getcwd()

  os.chdir(dir)

  files = glob.glob('*')
  files = abspath(files)

  list = []

  # Exclude files
  for rule in exclude_rules:
    logging.debug("filtering ", rule)

    exclude_files = glob.glob(rule)
    exclude_files = abspath(exclude_files)

    files = diff(files, exclude_files)

    for exclude_file in exclude_files:
     logging.debug("excluding ", exclude_file)

  # Include files
  for rule in include_rules:
    logging.debug("adding ", rule)

    include_files = glob.glob(rule)
    include_files = abspath(include_files)

    include_files = union(files, include_files)

    for include_file in include_files:
     logging.debug("including", include_file)

  # Recurse directories
  for file in files:
    logging.debug("including ", file)

    if(os.path.isdir(file)):
      temp = match_files(file, exclude_rules, include_rules)
      list.extend(temp)
    else:
      list.append(file)

  os.chdir(working_dir)

  return list
  
###################################################################################################
# Project
###################################################################################################

def prepare_project_template(buffer, version='VS2008'):
  if(version == 'VS2008'):
    begintag = '<Files>'
    endtag = '</Files>'

    return buffer[0:buffer.find(begintag)] \
      + '<Files>\n' \
      + '    <Filter Name="Header Files" Filter="h;hpp;hxx;hm;inl;inc;xsd" UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}" >\n$header$\n    </Filter>\n' \
      + '    <Filter Name="Source Files" Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx" UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}" >\n$source$\n    </Filter>\n' \
      + '    <Filter Name="Resource Files" Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx" UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}" >\n$resource$\n    </Filter>\n' \
      + '  </Files>'\
      + buffer[(buffer.rfind(endtag) + len(endtag)):len(buffer)]
  else:
    begintag = '<ItemGroup>'
    endtag = '</ItemGroup>'
    
    index = buffer.find(endtag) + len(endtag)
      

    return buffer[0:buffer.find(begintag, index)] \
      + '<ItemGroup>\n$source$\n  </ItemGroup>\n' \
      + '  <ItemGroup>\n$header$\n  </ItemGroup>\n' \
      + '  <ItemGroup>\n$resource$\n  </ItemGroup>' \
      + buffer[(buffer.rfind(endtag) + len(endtag)):len(buffer)]
      
def create_project(template_file, files, project_file, version='VS2008'):
  working_dir = os.getcwd()

  project_dir = os.path.dirname(project_file)

  # TODO: don't have to chdir to get relpath to work
  os.chdir(project_dir)

  source = ""
  header = ""
  resource = ""

  for file in files:
    filename, filext = os.path.splitext(file)
    relpath = os.path.relpath(file)

    if(version == 'VS2008'):
      if(filext == '.c'):
        source += '\n      <File RelativePath=\'' + relpath + '\' />'
      elif(filext == '.h'):
        header += '\n      <File RelativePath=\'' + relpath + '\' />'
      else:
        resource += '\n      <File RelativePath=\'' + relpath + '\' />'
    else:
      if(filext == '.c'):
        source += '\n    <ClCompile Include=\'' + relpath + '\' />'
      elif(filext == '.h'):
        header += '\n    <ClInclude Include=\'' + relpath + '\' />'
      else:
        resource += '\n    <None Include=\'' + relpath + '\' />'

  source = source[1:]
  header = header[1:]
  resource = resource[1:]

  os.chdir(working_dir)

  fin = open(template_file, 'r')
  buffer = fin.read()

  buffer = prepare_project_template(buffer, version)

  buffer = buffer.replace('$source$', source)
  buffer = buffer.replace('$header$', header)
  buffer = buffer.replace('$resource$', resource)

  fout = open(project_file, 'w')
  fout.write(buffer)
  
###################################################################################################
# Filters
###################################################################################################
  
def prepare_filters_template(buffer, version='VS2010'):
  if(version == 'VS2008'):
    print >> sys.stderr, 'Visual Studio 2008 does not have a separate filter file.'
    parser.print_help()
    sys.exit(-1)
  else:
    begintag = '<ItemGroup>'
    endtag = '</ItemGroup>'
    
    index = buffer.rfind(begintag) - 1
    
    return buffer[0:buffer.find(begintag, 0, index)] \
      + '<ItemGroup>\n$source$\n  </ItemGroup>\n' \
      + '  <ItemGroup>\n$header$\n  </ItemGroup>\n' \
      + '  <ItemGroup>\n$resource$\n  </ItemGroup>' \
      + buffer[(buffer.rfind(endtag, 0, index) + len(endtag)):len(buffer)]
      
def create_filters(template_file, files, project_file, version='VS2010'):
  if(version == 'VS2008'):
    print >> sys.stderr, 'Visual Studio 2008 does not have a separate filter file.'
    parser.print_help()
    sys.exit(-1)
    
  working_dir = os.getcwd()

  project_dir = os.path.dirname(project_file)

  # TODO: don't have to chdir to get relpath to work
  os.chdir(project_dir)

  source = ""
  header = ""
  resource = ""

  for file in files:
    filename, filext = os.path.splitext(file)
    relpath = os.path.relpath(file)

    if(filext == '.c'):
      source += '\n    <ClCompile Include=\'' + relpath + '\' >\n' \
        + '      <Filter>Source Files</Filter>\n' \
        + '    </ClCompile>'
    elif(filext == '.h'):
      header += '\n    <ClInclude Include=\'' + relpath + '\' >\n' \
        + '      <Filter>Header Files</Filter>\n' \
        + '    </ClInclude>'
    else:
      resource += '\n    <None Include=\'' + relpath + '\' >\n' \
        + '      <Filter>Resource Files</Filter>\n' \
        + '    </None>'

  source = source[1:]
  header = header[1:]
  resource = resource[1:]

  os.chdir(working_dir)

  fin = open(template_file, 'r')
  buffer = fin.read()

  buffer = prepare_filters_template(buffer, version)

  buffer = buffer.replace('$source$', source)
  buffer = buffer.replace('$header$', header)
  buffer = buffer.replace('$resource$', resource)

  fout = open(project_file, 'w')
  fout.write(buffer)
  
###################################################################################################
# Arguments
###################################################################################################

def parse_args():
  path = os.path.dirname(os.path.abspath(__file__))

  # Parse command line arguments
  parser = OptionParser(usage='usage: %prog [options] -d DIRECTORY')

  # Input
  parser.add_option("-d", "--directory", action="store", type="string",
    dest="project_dir", default="",
    help="DIRECTORY to create the new project file in", metavar="DIRECTORY")

  parser.add_option("-I", "--exclude", action="store", type="string",
    dest="exclude_file", default=os.path.join(path, "EDK.gitignore"),
    help="exclude files matching patterns in FILE", metavar="FILE")

  parser.add_option("-v", "--version", action="store", type="string",
    dest="version", default=os.path.join(path, "VS2008"),
    help="version of Visual Studio project to create", metavar="VERSION")

  # Output
  parser.add_option("-t", "--template", action="store", type="string",
    dest="template_file",
    help="project FILE to use as template", metavar="FILE")
  parser.add_option("-o", "--output", action="store", type="string",
    dest="project_file", default=os.path.join(path, "output.vcproj"),
    help="project FILE to create", metavar="FILE")

  (options, args) = parser.parse_args()

  if(options.project_dir == ""):
    print >> sys.stderr, 'DIRECTORY to create the new project file in is required.'
    parser.print_help()
    sys.exit(-1)

  if(options.template_file == ""):
    print >> sys.stderr, 'project FILE to use as template is required.'
    parser.print_help()
    sys.exit(-1)

  return options
  
###################################################################################################
# Main
###################################################################################################

def main():
  # Parse command line arguments
  options = parse_args()

  # Load file exclusion rules
  (exclude_rules, include_rules) = load_rules(options.exclude_file)

  # Walk the tree and collect files
  files = match_files(options.project_dir, exclude_rules, include_rules)

  # Create project file from template
  create_project(options.template_file, files, options.project_file, options.version)
  
  # Create filters file from template
  if(options.version != 'VS2008'):
    create_filters(options.template_file + '.filters', files, options.project_file + '.filters', options.version)

main()